package main.java.utils.common;

import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.StreamSupport;

public class IterableUtil {
	public static <T> Iterable<T> concat(Iterable<Iterable<T>> iterables) {
		return StreamSupport.stream(iterables.spliterator(), false)
				.flatMap(iterable -> StreamSupport.stream(iterable.spliterator(), false))
				::iterator;
	}

	@SafeVarargs
	public static <T> Iterable<T> concat(Iterable<T>... iterables) {
		return concat(Arrays.asList(iterables));
	}

	public static <L, T> Iterable<T> group(Iterable<L> iterable, BiPredicate<L, L> group, Function<List<L>, T> grouper) {
		return () -> new Iterator<T>() {
			private Iterator<L> iterator = iterable.iterator();
			private List<L> lines = new ArrayList<>();
			private T next;

			private T group() {
				T apply = grouper.apply(new ArrayList<>(lines));
				lines.clear();
				return apply;
			}

			@Override
			public boolean hasNext() {
				for (; iterator.hasNext(); ) {
					L line = iterator.next();
					if (line == null) {
					} else if (!lines.isEmpty() && !group.test(lines.get(lines.size() - 1), line)) {
						next = group();
						lines.add(line);
						return true;
					} else {
						lines.add(line);
					}
				}
				if (lines.isEmpty()) {
					return false;
				}
				next = group();
				return true;
			}

			@Override
			public T next() {
				return next;
			}
		};
	}

	public static <E> Iterable<E> iterable(Enumeration<E> enumeration) {
		return () -> new Iterator<E>() {
			@Override
			public boolean hasNext() {
				return enumeration.hasMoreElements();
			}

			@Override
			public E next() {
				return enumeration.nextElement();
			}
		};
	}

	public static <T extends Comparable<T>> void merge(Iterable<T> left, Iterable<T> right, BiConsumer<T, T> handler) {
		merge(left, right, Comparable::compareTo, handler);
	}

	// two pointers
	public static <T> void merge(Iterable<T> left, Iterable<T> right, Comparator<T> comparator, BiConsumer<T, T> handler) {
		Iterator<T> li = left.iterator(), ri = right.iterator();
		for (T l = null, r = null; ; ) {
			if (l == null && li.hasNext()) {
				l = li.next();
			}
			if (r == null && ri.hasNext()) {
				r = ri.next();
			}
			if (l == null && r == null) {
				break;
			} else if (r == null || l != null && comparator.compare(l, r) < 0) {
				handler.accept(l, null);
				l = null;
			} else if (l == null || 0 < comparator.compare(l, r)) {
				handler.accept(null, r);
				r = null;
			} else {
				handler.accept(l, r);
				l = null;
				r = null;
			}
		}
	}

	public static <T extends Comparable<T>> void merge(Iterable<T> left, Iterable<T> right, Consumer<T> handler) {
		merge(left, right, Comparable::compareTo, handler);
	}

	public static <T> void merge(Iterable<T> left, Iterable<T> right, Comparator<T> comparator, Consumer<T> handler) {
		merge(left, right, comparator, (t1, t2) -> {
			if (t1 != null) {
				handler.accept(t1);
			} else if (t2 != null) {
				handler.accept(t2);
			}
		});
	}
}
